﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using HtmlAgilityPack;
using Kooboo.HtmlFunc.Function;
using Kooboo.HtmlFunc.Providers;

namespace Kooboo.HtmlFunc.Analysis
{
    public class Analyzer
    {
        static HashSet<Type> VisitorTypes;
        static HashSet<VisitorBase> Visitors;
        static HashSet<Type> EmbeddedVisitorTypes;

        static ReaderWriterLockSlim VisitorsLocker;
        static ReaderWriterLockSlim VisitorTypesLocker;

        static Analyzer()
        {
            VisitorTypes = new HashSet<Type>();
            Visitors = new HashSet<VisitorBase>();
            VisitorsLocker = new ReaderWriterLockSlim();
            VisitorTypesLocker = new ReaderWriterLockSlim();

            // register defalut visitor types
            EmbeddedVisitorTypes = new HashSet<Type>();
            EmbeddedVisitorTypes.Add(typeof(Visitors.HtmlPartVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.ViewPartVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.PositionVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.HtmlBlockPartVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.LayoutVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.LabelVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.PageSettingVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.MenuVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.MenuItemVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.MenuLinkVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.MenuLinkTextVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.MenuCurrentClassVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.ListVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.ListItemVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.ListItemLinkVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.ListItemDataVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.ListItemEditableVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.DetailVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.DetailDataVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.CustomVisitor));
            //EmbeddedVisitorTypes.Add(typeof(Visitors.InnerPageVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.HtmlTagVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.HeadTagVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.TitleTagVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.MetaTagVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.BodyTagVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.ImgTagVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.StyleTagVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.ScriptTagVisitor));
            EmbeddedVisitorTypes.Add(typeof(Visitors.AnchorTagVisitor));
        }

        public static void Register(Type type)
        {
            var ancestor = typeof(VisitorBase);
            if (!RuntimeKit.IsInherited(type, ancestor))
            {
                throw new ArgumentException(string.Format("The type {0} you register is not a {1} type!", type.Name, ancestor.Name));
            }

            VisitorTypesLocker.EnterWriteLock();
            try
            {
                if (!VisitorTypes.Contains(type))
                {
                    VisitorTypes.Add(type);
                }
            }
            finally
            {
                VisitorTypesLocker.ExitWriteLock();
            }
        }

        public static bool Unregister(Type type)
        {
            VisitorTypesLocker.EnterWriteLock();
            try
            {
                return VisitorTypes.Remove(type);
            }
            finally
            {
                VisitorTypesLocker.ExitWriteLock();
            }
        }

        public static void Register(VisitorBase visitor)
        {
            if (visitor == null)
            {
                throw new ArgumentNullException();
            }

            VisitorsLocker.EnterWriteLock();
            try
            {
                if (!Visitors.Contains(visitor))
                {
                    Visitors.Add(visitor);
                }
            }
            finally
            {
                VisitorsLocker.ExitWriteLock();
            }
        }

        public static bool Unregister(VisitorBase visitor)
        {
            VisitorsLocker.EnterWriteLock();
            try
            {
                return Visitors.Remove(visitor);
            }
            finally
            {
                VisitorsLocker.ExitWriteLock();
            }
        }

        public Analyzer()
        { }

        public Analyzer(string fileName, string pageHtml)
            : this()
        {
            this.FileName = fileName;
            this.PageHtml = pageHtml;
        }

        public Analyzer(string fileName, string pageHtml, bool throwException)
            : this(fileName, pageHtml)
        {
            this.ThrowException = throwException;
        }

        public string FileName
        {
            get;
            set;
        }

        public string PageHtml
        {
            get;
            set;
        }

        public bool ThrowException
        {
            get;
            set;
        }

        public IEnumerable<IAnalyseResult> Analyse()
        {
            var document = new HtmlDocument();
            document.LoadHtml(this.PageHtml);

            var context = new VisitorContext();
            context.FileName = this.FileName;
            context.ThrowException = this.ThrowException;

            var innerPages = new List<IInnerPage>();
            context.Items[FuncType.None] = innerPages;

            var visitorDispenser = new VisitorDispenser(context);
            visitorDispenser.Accept(new Visitors.InnerPageVisitor(context));
            visitorDispenser.Visit(document);

            var htmlParts = new List<PageHtmlPart>();
            // this must be loop by inverted order, 
            // for there can get a correct reference NextSibling.
            for (var i = innerPages.Count - 1; i > -1; i--)
            {
                var page = innerPages[i];
                var part = new PageHtmlPart(page);
                part.ParentNode = page.StartNode.ParentNode;
                part.NextSibling = page.EndNode.NextSibling;
                part.Isolate();
                htmlParts.Insert(0, part); // ensure the page index from top to down
            }

            var results = new List<IAnalyseResult>();
            var fileExtension = System.IO.Path.GetExtension(this.FileName);
            for (var i = 0; i < htmlParts.Count; i++)
            {
                var part = htmlParts[i];
                part.MergeToDoc();
                var html = document.ReadString();
                part.SeparateFormDoc();
                // analyse core
                var fileName = part.InnerPage.Name + fileExtension;
                var itemResult = AnalyseCore(fileName, html, this.ThrowException);
                itemResult.Layout.Name = System.IO.Path.GetFileNameWithoutExtension(this.FileName);
                itemResult.PageSetting.Layout = itemResult.Layout.Name;
                results.Add(itemResult);
            }

            if (results.Count == 0)
            {
                results.Add(AnalyseCore(this.FileName, this.PageHtml, this.ThrowException));
            }

            return results;
        }

        private static IAnalyseResult AnalyseCore(string fileName, string pageHtml, bool throwException)
        {
            var context = new VisitorContext();
            context.FileName = fileName;
            context.ThrowException = throwException;

            var visitorDispenser = new VisitorDispenser(context);

            // 1.embedded types
            foreach (var type in EmbeddedVisitorTypes)
            {
                var visitor = (VisitorBase)RuntimeKit.CreateInstance(type, context);
                if (visitor != null)
                {
                    visitorDispenser.Accept(visitor);
                }
            }

            // 2.register types
            VisitorTypesLocker.EnterReadLock();
            try
            {
                foreach (var type in VisitorTypes)
                {
                    var visitor = (VisitorBase)RuntimeKit.CreateInstance(type);
                    if (visitor != null)
                    {
                        visitor.Context = context;
                        visitorDispenser.Accept(visitor);
                    }
                }
            }
            finally
            {
                VisitorTypesLocker.ExitReadLock();
            }

            // 3.register instances
            VisitorsLocker.EnterReadLock();
            try
            {
                foreach (var visitor in Visitors)
                {
                    visitor.Context = context;
                    visitorDispenser.Accept(visitor);
                }
            }
            finally
            {
                VisitorsLocker.ExitReadLock();
            }

            var document = new HtmlDocument();
            document.LoadHtml(pageHtml);
            visitorDispenser.Visit(document);

            if (string.IsNullOrEmpty(context.Result.PageSetting.Name))
                context.Result.PageSetting.Name = System.IO.Path.GetFileNameWithoutExtension(fileName);

            // ret
            return context.Result;
        }
    }

    class PageHtmlPart
    {
        public PageHtmlPart(IInnerPage innerPage)
        {
            this.InnerPage = innerPage;
            this.ContentNodes = new List<HtmlNode>();

            var next = innerPage.StartNode.NextSibling;
            while (next != innerPage.EndNode)
            {
                this.ContentNodes.Add(next);
                next = next.NextSibling;
            }
        }

        public IInnerPage InnerPage
        {
            get;
            set;
        }

        public HtmlNode NextSibling
        {
            get;
            set;
        }

        public HtmlNode ParentNode
        {
            get;
            set;
        }

        public IList<HtmlNode> ContentNodes
        {
            get;
            private set;
        }

        public bool RemoveInterlanguage
        {
            get
            {
                return true;
            }
        }

        public void Isolate()
        {
            this.InnerPage.StartNode.RemoveTo(this.InnerPage.EndNode);
        }

        public void SeparateFormDoc()
        {
            if (!this.RemoveInterlanguage)
            {
                this.InnerPage.StartNode.ParentNode.RemoveChild(this.InnerPage.StartNode);
            }
            for (var i = 0; i < this.ContentNodes.Count; i++)
            {
                var node = this.ContentNodes[i];
                node.ParentNode.RemoveChild(node);
            }
            if (!this.RemoveInterlanguage)
            {
                this.InnerPage.EndNode.ParentNode.RemoveChild(this.InnerPage.EndNode);
            }
        }

        public void MergeToDoc()
        {
            if (this.NextSibling == null)
            {
                if (!this.RemoveInterlanguage)
                {
                    this.ParentNode.AppendChild(this.InnerPage.StartNode);
                }
                for (var i = 0; i < this.ContentNodes.Count; i++)
                {
                    this.ParentNode.AppendChild(this.ContentNodes[i]);
                }
                if (!this.RemoveInterlanguage)
                {
                    this.ParentNode.AppendChild(this.InnerPage.EndNode);
                }
            }
            else
            {
                if (!this.RemoveInterlanguage)
                {
                    this.ParentNode.InsertBefore(this.InnerPage.StartNode, this.NextSibling);
                }
                for (var i = 0; i < this.ContentNodes.Count; i++)
                {
                    this.ParentNode.InsertBefore(this.ContentNodes[i], this.NextSibling);
                }
                if (!this.RemoveInterlanguage)
                {
                    this.ParentNode.InsertBefore(this.InnerPage.EndNode, this.NextSibling);
                }
            }
        }
    }
}
